#region
/* Adoor.NET - Object/Relational mapping framework leveraging ADO.NET`s powerful disconnected mode.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *  
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#endregion

using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.ComponentModel;
using Adoor.Expression;
using Adoor.Expression.AST;
using Adoor.Object.Domain;
using Adoor.Relational;
using Adoor.Object;
using Adoor.Object.Query;
using Adoor.Object.Entity;
using Adoor.Object.Disconnected;
using Adoor.Object.Relational;

namespace Adoor.Object.Relational
{
    public class DataSetObjectStorage : Component, IObjectSource, IObjectStorage, Adoor.Object.Entity.ICacheCommitable
    {
        private IServiceProvider serviceProvider;
        private ObjectContext context;
        private IDataSetRelationalStore relationalCache;
        
        private Adoor.Data.TransactionalDataSetLite transactionalDataSetHelper;

        public DataSetObjectStorage()
            : this(new DataSet())
        {
        }

        private IObjectResolver objectResolver;

        public IObjectResolver ObjectResolver
        {
            get { return objectResolver; }
            set { objectResolver = value; }
        }

        public DataSetObjectStorage(DataSet dataSet)
        {
            this.relationalCache = new DataSetRelationalStore(dataSet);
            this.transactionalDataSetHelper = new Adoor.Data.TransactionalDataSetLite(this.DataSet);
       
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            if (disposing)
                ((IDisposable)relationalCache).Dispose();
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IServiceProvider ServiceProvider
        {
            get { return this.serviceProvider; }
            set
            {
                this.serviceProvider = value;
                this.context = (ObjectContext)this.serviceProvider.GetService(typeof(ObjectContext));
            }
        }

        [Browsable(false)]
        public ObjectContext Context
        {
            get { return this.context; }
        }

        private ChildDomainCollection childDomains = new ChildDomainCollection();
        public ChildDomainCollection ChildDomains
        {
            get { return this.childDomains; }
        }

        [Browsable(false)]
        public IObjectStorage LocalStorage
        {
            get { return this; }
        }

        public string GetXml()
        {
            return this.relationalCache.DataSet.GetXml();
        }

        public object Create(Type type, params object[] args)
        {
            IEntityFactory factory = ObjectResolver.FindFactory(type);
            return factory.Create(this, args);
        }

        public T Create<T>(params object[] args)
        {
            return (T) Create(typeof(T), args);
        }

        public void Clear()
        {
            foreach (ObjectDomain domain in ObjectResolver.ManagedDomains)
                ClearDomain(domain);
        }

        public void Cancel()
        {
            foreach (ObjectDomain domain in ObjectResolver.ManagedDomains)
                Cancel(domain);
            
        }

        protected void Cancel(ObjectDomain domain)
        {
            foreach (ObjectDomain child in domain.References)
                Cancel(child);

            foreach (EntityData entityData in domain.EntityInfos)
            {
                IEntityFactory factory = (IEntityFactory)entityData.GetService(typeof(IEntityFactory));
                if (factory != null)
                    factory.Cancel(this);
            }
        }


        protected void ClearDomain(ObjectDomain domain)
        {
            foreach (EntityData entityData in domain.EntityInfos)
            {
                IEntityFactory f = (IEntityFactory)entityData.GetService(typeof(IEntityFactory));
                if (f != null)
                    f.Clear(this);
            }
        }

        [Obsolete("Does it make sense ?? in transactionalDatasetSenario ??")] 
        public IObjectStorage Clone(CloneMode mode)
        {
            DataSet dataSet;
            switch (mode)
            {
                case CloneMode.Empty:
                    dataSet = new DataSet();
                    break;
                case CloneMode.NoData:
                    dataSet = this.DataSet.Clone();
                    break;
                case CloneMode.NewData:
                    dataSet = this.DataSet.GetChanges();
                    if (dataSet == null)
                        dataSet = new DataSet();
                    break;
                case CloneMode.Full:
                default:
                    dataSet = this.DataSet.Copy();
                    break;
            }
            DataSetObjectStorage result = new DataSetObjectStorage(dataSet);
            result.CheckIntegrity = this.CheckIntegrity;
            return result;
        }

        public DataRow CreateRow(string tableName, ICollection<DataColumn> dataColumns)
        {
            return this.relationalCache.CreateRow(tableName, dataColumns);
        }

        public void Delete(object entity)
        {
            if (entity == null)
                throw new ArgumentNullException("entity");
            // TODO: integrity constraints ?
            EntityData ent = ((IEntity)(entity)).State.EntityFactory.EntityData;
            foreach (DependantEntity de in ent.Domain.ObjectDependencies[ent]){
                if (de.BackReferenceNuller != null)
                {
                    DataTable dt = ((DataSetObjectStorage)(((IEntity)entity).State).Storage).DataSet.Tables[de.ForeignEntity.EntityType.Name];
                    if (dt != null)
                    {
                        DataRow[] rows = dt.Select(de.BackReferenceNuller.ForeignKey + "=" + ((IEntity)entity).ToString());
                        foreach(DataRow row in rows)
                            row[de.BackReferenceNuller.ForeignKey] = DBNull.Value;
                    }
                    //   if (de.BackReferenceNuller != null)
                    //     de.BackReferenceNuller.SetNullValue(((IEntity)entity));
                }//backref
            }
                
            ((IEntity)entity).State.Delete();
        }

        public DataView GetIndex(string tableName, string key)
        {
            return this.relationalCache.GetIndex(tableName, key);
        }

        public void FillTable(string tableName, ICollection<DataColumn> dataColumns, DataView view)
        {
            this.relationalCache.FillTable(tableName, dataColumns, view);
        }

        public DataTable GetTable(string tableName, ICollection<DataColumn> dataColumns)
        {
            return this.relationalCache.GetTable(tableName, dataColumns);
        }

        [Browsable(false)]
        public DataSet DataSet
        {
            get { return this.relationalCache.DataSet; }
        }

        public void DeleteAll(Type type)
        {
            this.DeleteAll(this.ObjectResolver.GetPath(type), null);
        }

        public void DeleteAll(ObjectPath path, params object[] args2)
        {
            System.Collections.ICollection args = RelationalQueryGenerator.GenerateArgs(args2);
            RelationalOperator rq = RelationalQueryGenerator.GenerateOperator(MappingStrategyEnum.Local, path);
            this.relationalCache.ExecuteDelete(rq, args);
        }

        public void DeleteAll(string query, params object[] args)
        {
            DeleteAll(this.ObjectResolver.ParsePath(query), args);
        }

        public void Fill(DataSet dataset, Type type)
        {
            DataTable table = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
            dataset.Tables.Add(table);
            Fill(table, type);
        }

        public void Fill(DataSet dataset, ObjectQuery query, params object[] args)
        {
            DataTable table = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
            dataset.Tables.Add(table);
            Fill(table, query, args);
        }

        public void Fill(DataSet dataset, string query, params object[] args)
        {
            DataTable table = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
            dataset.Tables.Add(table);
            Fill(table, query, args);
        }

        public void Fill(DataSet dataset, string query, IDictionary<string, object> args)
        {
            DataTable table = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
            dataset.Tables.Add(table);
            Fill(table, query, args);
        }

        public void Fill(DataSet dataset, string tableName, Type type)
        {
            DataTable table = dataset.Tables[tableName];
            if (table == null)
            {
                table = new DataTable(tableName);
                dataset.Tables.Add(table);
            }
            dataset.Tables.Add(table);
            Fill(table, type);
        }

        public void Fill(DataSet dataset, string tableName, ObjectQuery query, params object[] args)
        {
            DataTable table = dataset.Tables[tableName];
            if (table == null)
            {
                table = new DataTable(tableName);
                dataset.Tables.Add(table);
            }
            dataset.Tables.Add(table);
            Fill(table, query, args);
        }

        public void Fill(DataSet dataset, string tableName, string query, params object[] args)
        {
            DataTable table = dataset.Tables[tableName];
            if (table == null)
            {
                table = new DataTable(tableName);
                dataset.Tables.Add(table);
            }
            dataset.Tables.Add(table);
            Fill(table, query, args);
        }

        public void Fill(DataTable table, Type type)
        {
            Fill(table, new ObjectQuery(this.ObjectResolver.GetPath(type)));
        }

        public void Fill(DataTable table, string query, params object[] args)
        {
            Fill(table, this.ObjectResolver.ParseQuery(query), args);
        }

        public void Fill(DataTable table, string query, IDictionary<string, object> args)
        {
            Fill(table, this.ObjectResolver.ParseQuery(query), args);
        }

        public void Fill(DataTable table, ObjectQuery query, params object[] args)
        {
            System.Collections.ICollection rargs = RelationalQueryGenerator.GenerateArgs(args);
            RelationalQuery rq = RelationalQueryGenerator.GenerateQuery(MappingStrategyEnum.Local, query);
            this.relationalCache.Fill(rq, rargs, table);
        }

        public void Fill(DataTable table, ObjectQuery query, IDictionary<string, object> args)
        {
            IDictionary<string, object> rargs = RelationalQueryGenerator.GenerateArgs(args);
            RelationalQuery rq = RelationalQueryGenerator.GenerateQuery(MappingStrategyEnum.Local, query);
            this.relationalCache.Fill(rq, rargs, table);
        }

        public object GetScalar(ObjectQuery query, params object[] args)
        {
            System.Collections.ICollection rargs = RelationalQueryGenerator.GenerateArgs(args);
            RelationalQuery rq = RelationalQueryGenerator.GenerateQuery(MappingStrategyEnum.Local, query);
            return this.relationalCache.GetScalar(rq, rargs);
        }

        public object GetScalar(string query, params object[] args)
        {
            return GetScalar(this.ObjectResolver.ParseQuery(query), args);
        }

        public Int64 GetCount(Type type)
        {
            return this.GetCount(new ObjectQuery(this.ObjectResolver.GetPath(type)), null);
        }

        public Int64 GetCount(ObjectQuery query, params object[] args2)
        {
            System.Collections.ICollection args = RelationalQueryGenerator.GenerateArgs(args2);
            RelationalQuery rq = RelationalQueryGenerator.GenerateQuery(MappingStrategyEnum.Local, query);
            return this.relationalCache.ExecuteCount(rq, args);
        }

        public Int64 GetCount(string query, params object[] args)
        {
            return GetCount(this.ObjectResolver.ParseQuery(query), args);
        }

        public IDataReader GetDataReader(Type type)
        {
            throw new NotImplementedException();
        }

        public IDataReader GetDataReader(ObjectQuery query, params object[] args)
        {
            throw new NotImplementedException();
        }

        public IDataReader GetDataReader(string query, params object[] args)
        {
            return GetDataReader(this.ObjectResolver.ParseQuery(query), args);
        }

        public object Find(Type baseType, string oid)
        {
            EntityData entityData = this.ObjectResolver.FindEntityData(baseType);
            if (entityData == null)
                throw new Exception("Actual entity data not found for type " + baseType.FullName);
            EntityData actualEntityData = entityData.GetActualEntityData(oid);
            return InternalFind(actualEntityData, oid);
        }

        public T Find<T>(Type baseType, string oid)
        {
            EntityData entityData = this.ObjectResolver.FindEntityData(baseType);
            if (entityData == null)
                throw new Exception("Actual entity data not found for type " + baseType.FullName);
            EntityData actualEntityData = entityData.GetActualEntityData(oid);
            return (T)InternalFind(actualEntityData, oid);
        }

        public object Find(EntityData entityData, string oid)
        {
            if (oid == null)
                return null;
            //EntityData actualEntityData = entityData.OidData.GetActualEntityData(oid);
            EntityData actualEntityData = entityData.GetActualEntityData(oid);
            if (actualEntityData == null)
                throw new Exception("Actual entity data not found for type " + entityData.EntityType.Name);
            return InternalFind(actualEntityData, oid);
        }

        public T Find<T>(EntityData entityData, string oid)
        {
            if (oid == null)
                return default(T);
            //EntityData actualEntityData = entityData.OidData.GetActualEntityData(oid);
            EntityData actualEntityData = entityData.GetActualEntityData(oid);
            if (actualEntityData == null)
                throw new Exception("Actual entity data not found for type " + entityData.EntityType.Name);
            return (T)InternalFind(actualEntityData, oid);
        }

        private object InternalFind(EntityData entityData, string oid)
        {
            if (oid == "" || oid == null)
                return null;

            IEntityFactory factory = (IEntityFactory)entityData.GetService(typeof(IEntityFactory));
            IEntity result = factory.Find(this, oid);
            if (result != null && result.State.Status == EntityStatus.Deleted)
                return null;

            return result;
        }

        public System.Collections.ICollection Collect(ObjectQuery query, object args)
        {
            if (args == null)
                return Collect(query, new object[0]);
            else if (args is IDictionary<string, object>)
                return Collect(query, (IDictionary<string, object>)args);
            else if (args is System.Collections.ICollection)
                return Collect(query, (object[])args);
            else
                throw new NotSupportedException();
        }

        public System.Collections.ICollection Collect(ObjectQuery query, IDictionary<string, object> dicArgs)
        {
            ObjectPath op = query.Path;  // ensure query is parsed first

            Correlation correlation = null;
            if (query.SelectColumns == null)
            {
                if (query.Path.Correlations.Count == 1)
                {
                    IEnumerator<Correlation> values = query.Path.Correlations.Values.GetEnumerator();
                    values.MoveNext();
                    correlation = values.Current;
                }
                else
                    throw new Exception("missing select clause");
            }
            else if (query.SelectColumns.Length == 1)
            {
                ExpressionName en = query.SelectColumns[0].Expr as ExpressionName;
                if (en != null)
                {
                    string correlName = en.Value;
                    if (en.Qualifier != "")
                        correlName = en.Qualifier;

                    if (!query.Path.Correlations.TryGetValue(correlName, out correlation))
                        throw new Exception("Correlation " + correlName + " not found");
                }
                else
                    throw new Exception("invalid select clause");
            }
            else
                throw new Exception("missing select clause");
            EntityData entityData = correlation.EntityData;

            IDictionary<string, object> args = RelationalQueryGenerator.GenerateArgs(dicArgs);
            RelationalOperator rq = RelationalQueryGenerator.GenerateOperator(MappingStrategyEnum.Local, op);

            IKeyMappingStrategy mappingStrategy = (IKeyMappingStrategy)entityData.OidData;
            RelationalQuery rq2 = mappingStrategy.Project(rq, correlation.Name);

            DataTable table = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
            this.relationalCache.Fill(rq2, args, table);
            DataView view = new DataView(table);


            Type objType = entityData.EntityType;
            Type colType = typeof(CollectCollection<>);
            Type finalType = colType.MakeGenericType(objType);
            System.Reflection.ConstructorInfo ci = finalType.GetConstructors()[0];
            return (System.Collections.ICollection)ci.Invoke(new object[] { entityData, this, view });
            //return (System.Collections.ICollection)new CollectCollection<object>(entityData, this, view);
        }

        public System.Collections.ICollection Collect(string query, IDictionary<string, object> dicArgs)
        {
            return Collect(this.ObjectResolver.ParseQuery(query), dicArgs);
        }

        public System.Collections.ICollection Collect(Type type)
        {
            EntityData entityData = this.ObjectResolver.FindEntityData(type);
            ObjectPath path = entityData.GetPath(entityData.EntityType.Name);

            System.Collections.ICollection args = new object[0];
            RelationalOperator rq = RelationalQueryGenerator.GenerateOperator(MappingStrategyEnum.Local, path);
            IKeyMappingStrategy mappingStrategy = (IKeyMappingStrategy)entityData.OidData;
            RelationalQuery rq2 = mappingStrategy.Project(rq, entityData.EntityType.Name);
            DataTable table = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
            this.relationalCache.Fill(rq2, args, table);

            Type colType = typeof(CollectCollection<>);
            Type finalType = colType.MakeGenericType(type);
            System.Reflection.ConstructorInfo ci = finalType.GetConstructors()[0];
            return (System.Collections.ICollection)ci.Invoke(new object[] { entityData, this, new DataView(table) });

            //return (System.Collections.ICollection)new CollectCollection<object>(entityData, this, new DataView(table));
        }

        public System.Collections.ICollection Collect(ObjectQuery query, params object[] args2)
        {
            ObjectPath op = query.Path;  // ensure query is parsed first

            Correlation correlation = null;
            if (query.SelectColumns == null)
            {
                if (query.Path.Correlations.Count == 1)
                {
                    IEnumerator<Correlation> values = query.Path.Correlations.Values.GetEnumerator();
                    values.MoveNext();
                    correlation = values.Current;
                }
                else
                    throw new Exception("missing select clause");
            }
            else if (query.SelectColumns.Length == 1)
            {
                ExpressionName en = query.SelectColumns[0].Expr as ExpressionName;
                if (en != null)
                {
                    string correlName = en.Value;
                    if (en.Qualifier != "")
                        correlName = en.Qualifier;

                    if (!query.Path.Correlations.TryGetValue(correlName, out correlation))
                        throw new Exception("Correlation " + correlName + " not found");
                }
                else
                    throw new Exception("invalid select clause");
            }
            else
                throw new Exception("missing select clause");
            EntityData entityData = correlation.EntityData;

            System.Collections.ICollection args = RelationalQueryGenerator.GenerateArgs(args2);
            RelationalOperator rq = RelationalQueryGenerator.GenerateOperator(MappingStrategyEnum.Local, op);

            IKeyMappingStrategy mappingStrategy = (IKeyMappingStrategy)entityData.OidData;
            RelationalQuery rq2 = mappingStrategy.Project(rq, correlation.Name);

            DataTable table = Adoor.Data.DataUtils.CreateCaseInsesitiveDataTable();
            this.relationalCache.Fill(rq2, args, table);
            DataView view = new DataView(table);

            Type objType = entityData.EntityType;
            Type colType = typeof(CollectCollection<>);
            Type finalType = colType.MakeGenericType(objType);
            System.Reflection.ConstructorInfo ci = finalType.GetConstructors()[0];
            return (System.Collections.ICollection)ci.Invoke(new object[] { entityData, this, view });



            //return new CollectCollection<object>(entityData, this, view);
        }

        public System.Collections.ICollection Collect(string query, params object[] args)
        {
            return Collect(this.ObjectResolver.ParseQuery(query), args);
        }

        public event ObjectSourceRetryEventHandler ObjectSourceRetry;

        protected virtual bool OnObjectSourceRetry(bool retry, Exception ex, out bool handled)
        {
            handled = true;
            ObjectSourceRetryEventArgs e = new ObjectSourceRetryEventArgs(retry, ex);
            if (ObjectSourceRetry != null)
            {
                foreach (Delegate dlg in ObjectSourceRetry.GetInvocationList())
                {
                    dlg.DynamicInvoke(new object[] { this, e });
                    if (e.Handled)
                    {
                        handled = e.Handled;
                        break;
                    }
                }
            }
            return e.Retry;
        }

        private bool checkIntegrity = false;
        public bool CheckIntegrity
        {
            get { return checkIntegrity; }
            set { checkIntegrity = value; }
        }

        public void ExecuteNonQuery(string query, IDictionary<string, object> args)
        {
            throw new NotImplementedException();
        }

        public void ExecuteNonQuery(ObjectQuery query, IDictionary<string, object> args)
        {
            throw new NotImplementedException();
        }

        public void ExecuteNonQuery(string query, params object[] args)
        {
            throw new NotImplementedException();
        }

        public void ExecuteNonQuery(ObjectQuery query, params object[] args)
        {
            throw new NotImplementedException();
        }

        public string Name
        {
            get { return DataSet.DataSetName; }
        }

        public void Enlist()
        {
            transactionalDataSetHelper.Enlist();
        }

        public void CommitLocalCache()
        {
            DataSet.AcceptChanges();
        }
    }
}